package tv.geir.slackspace.monitor;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Handles timing and performance monitoring.
 * 
 * @author Geir Gullestad Pettersen
 *
 */
public class PerformanceMonitor {
	
	private Integer monitorIdCounter = 0;
	
	private Map<Integer,MonitorKey> monitorKeys; 
	
	private static PerformanceMonitor instance = null;
	
	private static Log logger = LogFactory.getLog( PerformanceMonitor.class );
	
	private PerformanceMonitor() {
		monitorKeys = new LinkedHashMap<Integer,MonitorKey>();
	}
	
	public MonitorKey startMonitoring( int level, String serviceName) {
		MonitorKey key = null;
		
		Integer id = nextMonitorId();
		key = new MonitorKey( id, serviceName, level );

		monitorKeys.put( id, key );
		
		if( key != null && logger.isInfoEnabled() ) {
			log( key, Phase.START, 0 );
		}
		key.setStartTime( System.currentTimeMillis() );
		
		return key;
	}
	
	public void stopMonitoring( MonitorKey key ) {
		// null key is handed out if performance monitoring is disabled, so, ignore null income.
		long time = System.currentTimeMillis() - key.getStartTime();		
		if( key != null ) {
			if(	logger.isInfoEnabled()) {
				log( key, Phase.END, time );			
			} 
		}
		removeKey( key );
	}
	
	
	
	public void failMonitoring( MonitorKey key ) {
		// null key is handed out if performance monitoring is disabled, so, ignore null income.
		if( key != null && logger.isInfoEnabled()) {
			long time = System.currentTimeMillis() - key.getStartTime();
			log( key, Phase.FAIL, time );			
		}
		removeKey( key );
	}
	
	private void removeKey( MonitorKey key )  {
		Object removed = monitorKeys.remove( key.getMonitorId() );
		if( removed == null ) {
			logger.warn("Cannot remove monitor key, it has already been removed or never existed. monitorId=" + key.getMonitorId());
			throw new IllegalStateException( "Monitorkey does not exist (never created or already removed)");
		}
	}
	
	private void log( MonitorKey key, Phase p , long time) {
		if( logger.isInfoEnabled()) {
			logger.info( new StringBuilder( p.toString() )
			.append( ", ")
			.append( key.getLevel() )
			.append( ", [id=" )
			.append( key.getMonitorId() )
			.append( "], " )
			.append( key.getServiceName() )
			.append( ", " )
			.append( time ) 
			.toString() );			
		}			
	}
	
	
	public static PerformanceMonitor getMonitor() {
		if( instance == null ) {
			instance = new PerformanceMonitor();
		}
		return instance;
	}
	
	
	private synchronized Integer nextMonitorId() {
		return ++monitorIdCounter;
	}
	
	private enum Phase { 
		START("START"), END("END  "), FAIL("FAIL "); 
		 
		private String name;
		
		Phase( String name ) {
			this.name = name;
		}
		
		@Override
		public String toString() {
			return name;
		}
		
	};
	
	
	public class MonitorKey {
		private int level;
		private String serviceName;
		private Integer monitorId;
		private long startTime;
		
		protected MonitorKey( Integer monitorId, String serviceName, int level ) {
			this.monitorId = monitorId;
			this.serviceName  =serviceName;
			this.level = level;
		}
		
		protected long getStartTime() {
			return startTime;
		}
		protected void setStartTime(long startTime) {
			this.startTime = startTime;
		}
		protected int getLevel() {
			return level;
		}


		protected Object getMonitorId() {
			return monitorId;
		}

		protected String getServiceName() {
			return serviceName;
		}

		
	}
}
